﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.IO;
using System.Threading;
using Refl.Extensions;

namespace Refl.Meta.Compiling.Wrappers
{
    /// <summary>
    /// Class Wrapper for the IL disassembler ildasm.exe
    /// </summary>
    public static class ildasm
    {
        /// <summary>
        /// Invokes an instance of ildasm.exe using the given arguments.
        /// </summary>
        /// <returns>The standard output contents.</returns>
        public static string[] Inkove(string arguments)
        {
            string path = Paths.GetToolsPath();
            Process p = new Process();
            p.StartInfo = new ProcessStartInfo(path + "ildasm.exe", arguments);
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.RedirectStandardOutput = true;
            p.StartInfo.RedirectStandardError = true;
            p.StartInfo.CreateNoWindow = true;
            p.Start();
            string output = p.StandardOutput.ReadToEnd();
            string stderr = p.StandardError.ReadToEnd();
            p.WaitForExit();
            return new string[]{output, stderr};
        }
        /// <summary>
        /// Disassemble the specified file and save it as file.extension.il in the same folder as the executable.
        /// EX.: ildasm.Disassemble("C:/hello.exe"); if succeeded will generate a file called C:/hello.exe.il .
        /// </summary>
        /// <returns>The standard output contents of ildasm.exe</returns>
        public static string[] Disassemble(string filePath)
        {
            return ildasm.Inkove(filePath + " /output:" + filePath + ".il /nobar");
        }
        /// <summary>
        /// Disassemble the specified file and save it as file.extension.il in the same folder as the executable,
        /// with the possibility of preserving the original line data.
        /// EX.: ildasm.Disassemble("C:/hello.exe"); if succeeded will generate a file called C:/hello.exe.il .
        /// </summary>
        /// <param name="PreserveLineInfo">if true, ildasm will save the line numbers to the file.</param>
        /// <returns>The standard output contents of ildasm.exe</returns>
        public static string[] Disassemble(string filePath, bool PreserveLineInfo)
        {
            string li = "";
            if (PreserveLineInfo)
            {
                li = " /linenum";
            }
            return ildasm.Inkove(filePath + " /output:" + filePath + ".il /nobar" + li);
        }
    }
    /// <summary>
    /// Class wrapper for the IL Assembler ilasm.exe
    /// </summary>
    public static class ilasm
    {
        /// <summary>
        /// Invokes ilasm.exe with the given arguments.
        /// </summary>
        /// <returns>The standard output contents</returns>
        public static string[] Inkove(string arguments)
        {
            string path = Paths.GetFrameworkPath();
            Process p = new Process();
            p.StartInfo = new ProcessStartInfo(path + "ilasm.exe", arguments);
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.RedirectStandardOutput = true;
            p.StartInfo.RedirectStandardError = true;
            p.StartInfo.CreateNoWindow = true;
            p.Start();
            string output = p.StandardOutput.ReadToEnd();
            string stderr = p.StandardError.ReadToEnd();
            p.WaitForExit();
            return new string[] { output, stderr };
        }
        /// <summary>
        /// Assembles the given IL source file.
        /// </summary>
        /// <returns>The standard output contents</returns>
        public static string[] Assemble(string filePath)
        {
            if (!File.Exists(filePath))
            {
                throw new FileNotFoundException(filePath + " The specified file could not be found.");
            }
            return ilasm.Inkove(filePath);
        }
        /// <summary>
        /// Assembles the given IL source file and names the output.
        /// </summary>
        /// <returns>The standard output contents</returns>
        public static string[] Assemble(string filePath, string outputname)
        {
            if (!File.Exists(filePath))
            {
                throw new FileNotFoundException(filePath + " The specified file could not be found.");
            }
            return ilasm.Inkove(filePath + " /output:" + outputname);
        }
        /// <summary>
        /// Assembles the given IL source file into a Dynamic Link Library (DLL).
        /// </summary>
        /// <returns>The standard output contents</returns>
        public static string[] AssembleDll(string filePath)
        {
            if (!File.Exists(filePath))
            {
                throw new FileNotFoundException(filePath + " The specified file could not be found.");
            }
            return ilasm.Inkove(filePath + " /dll");
        }
        /// <summary>
        /// Assembles the given IL source file into a Dynamic Link Library (DLL) and names the output.
        /// </summary>
        /// <returns>The standard output contents</returns>
        public static string[] AssembleDll(string filePath, string outputname)
        {
            if (!File.Exists(filePath))
            {
                throw new FileNotFoundException(filePath + " The specified file could not be found.");
            }
            return ilasm.Inkove(filePath + " /dll" + " /output:" + outputname);
        }
        /// <summary>
        /// Assembles the given IL source code.
        /// </summary>
        /// <param name="source">The IL source code.</param>
        /// <param name="sourcefilename">The desired full name (with path) of the source file. (The IL source will be saved here).</param>
        /// <param name="overwrite">If the source file already exists on disk, this parameter sets if it should be overwritten.</param>
        /// <param name="isDll">Sets if the output is a DLL.</param>
        /// <param name="outputname">The name (with path) of the resulting assembly.</param>
        /// <returns>Returns the standard output contents.</returns>
        public static string[] AssembleFromSource(string source, string sourcefilename, bool overwrite, bool isDll, string outputname)
        {
            if (File.Exists(sourcefilename))
            {
                if (!overwrite)
                {
                    return null;
                }
            }
            File.WriteAllText(sourcefilename, source);
            if (isDll)
            {
                return ilasm.AssembleDll(sourcefilename, outputname);
            }
            else
            {
                return ilasm.Assemble(sourcefilename, outputname);
            }
        }
        /// <summary>
        /// Assemble the given IL source in the system temporary folder.
        /// </summary>
        /// <param name="source">The IL source code.</param>
        /// <param name="isDll">Sets if the output is DLL</param>
        /// <returns>Returns a three-element string array, in which the contents are respectively: the standard output contents from ilasm, the full path to the generated assembly, and the path to the saved source code.</returns>
        public static string[] AssembleFromSourceInTemp(string source, bool isDll, string dname)
        {
            string TMP = Paths.GetTempPath();
            string extension = ".exe"; if (isDll) { extension = ".dll"; }
            string [] r = ilasm.AssembleFromSource(source, TMP + dname + ".il", true, isDll, TMP + dname + extension);
            string[] returns =
            {
                r[0],r[1],
                (TMP+dname+extension),
                (TMP + dname + ".il")
            };
            return returns;
        }
    }
    public static class fsc
    {
        /// <summary>
        /// Invokes fsc.exe with the given arguments.
        /// </summary>
        /// <returns>The standard output contents</returns>
        public static string[] Inkove(string arguments)
        {
            string path = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
            if (!Directory.Exists(path)) { path = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); }

            path += "\\Microsoft F#\\v4.0\\fsc.exe";
            if (!File.Exists(path)) { throw new Exception("fsc.exe does not exist at \""+path+"\". Do you have F# installed?"); }
            Process p = new Process();
            p.StartInfo = new ProcessStartInfo(path, arguments);
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.RedirectStandardOutput = true;
            p.StartInfo.RedirectStandardError = true;
            p.StartInfo.CreateNoWindow = true;
            p.Start();

            p.WaitForExit();
            string output = p.StandardOutput.ReadToEnd();
            string stderr = p.StandardError.ReadToEnd();
            //reverses the changes made to the environment variables.
            return new string[] { output, stderr };
        }
        /// <summary>
        /// Compiles the given F# source file.
        /// </summary>
        /// <returns>The standard output contents</returns>
        public static string[] Compile(string filePath)
        {
            if (!File.Exists(filePath))
            {
                throw new FileNotFoundException(filePath + " The specified file could not be found.");
            }
            return fsc.Inkove("\"" + filePath + "\"");
        }
        /// <summary>
        /// Compiles the given F# source file into a Dynamic Link Library (DLL).
        /// </summary>
        /// <returns>The standard output contents</returns>
        public static string[] CompileDll(string filePath)
        {
            if (!File.Exists(filePath))
            {
                throw new FileNotFoundException(filePath + " The specified file could not be found.");
            }
            return fsc.Inkove("\"" + filePath + "\" --target:library");
        }
        /// <summary>
        /// Compiles the given F# source code.
        /// </summary>
        /// <param name="source">The F# source code.</param>
        /// <param name="sourcefilename">The desired full name (with path) of the source file. (The F# source will be saved here).</param>
        /// <param name="overwrite">If the source file already exists on disk, this parameter sets if it should be overwritten.</param>
        /// <param name="isDll">Sets if the output is a DLL.</param>
        /// <returns>Returns the standard output contents. null if file exists and it cannot be overwritten.</returns>
        public static string[] CompileFromSource(string source, string sourcefilename, bool overwrite, bool isDll)
        {
            if (File.Exists(sourcefilename))
            {
                if (!overwrite)
                {
                    return null;
                }
            }
            File.WriteAllText(sourcefilename, source);
            if (isDll)
            {
                return fsc.CompileDll(sourcefilename);
            }
            else
            {
                return fsc.Compile(sourcefilename);
            }
        }
        /// <summary>
        /// Compiles the given F# source in the system temporary folder.
        /// </summary>
        /// <param name="source">The F# source code.</param>
        /// <param name="isDll">Sets if the output is DLL</param>
        /// <returns>Returns a three-element string array, in which the contents are respectively: the standard output contents from cl, the full path to the generated assembly and the full path to the saved source code.</returns>
        public static string[] CompileFromSourceInTemp(string source, bool isDll, string dname)
        {
            string TMP = Paths.GetTempPath();
            string[] r = fsc.CompileFromSource(source, TMP + dname + ".fs", true, isDll);
            string[] returns =
            {
                r[0],r[1],
                (dname+(isDll ? ".dll" : ".exe")),
                (TMP + dname + ".fs")
            };
            return returns;
        }
    }
	public static class cl
    {
        /// <summary>
        /// Invokes cl.exe with the given arguments.
        /// </summary>
        /// <returns>The standard output contents</returns>
        public static string[] Inkove(string arguments)
        {
            string path = Paths.GetVisualStudioInstallationPath() + "\\VC\\bin\\";

            StringBuilder batch = new StringBuilder();
            batch.AppendLine("call \""+ Paths.GetVisualStudioInstallationPath() + "\\VC\\vcvarsall.bat\"");
            batch.AppendLine("\"" + path + "cl.exe\"" + " /clr " + arguments);
            File.WriteAllText("cldo.bat", batch.ToString());
            Process p = new Process();
            p.StartInfo = new ProcessStartInfo("cldo.bat");
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.RedirectStandardOutput = true;
            p.StartInfo.RedirectStandardError = true;
            p.StartInfo.CreateNoWindow = true;
            p.Start();
            
            p.WaitForExit();
            string output = p.StandardOutput.ReadToEnd();
            string stderr = p.StandardError.ReadToEnd();
            //reverses the changes made to the environment variables.
            return new string[]{output, stderr};
        }
        /// <summary>
        /// Compiles the given C++ source file.
        /// </summary>
        /// <returns>The standard output contents</returns>
        public static string[] Compile(string filePath)
        {
            if (!File.Exists(filePath))
            {
                throw new FileNotFoundException(filePath + " The specified file could not be found.");
            }
            return cl.Inkove("\"" + filePath + "\"");
        }
        /// <summary>
        /// Compiles the given C++ source file into a Dynamic Link Library (DLL).
        /// </summary>
        /// <returns>The standard output contents</returns>
        public static string[] CompileDll(string filePath)
        {
            if (!File.Exists(filePath))
            {
                throw new FileNotFoundException(filePath + " The specified file could not be found.");
            }
            return cl.Inkove("\"" + filePath + "\" /LD");
        }
        /// <summary>
        /// Compiles the given C++ source code.
        /// </summary>
        /// <param name="source">The C++ source code.</param>
        /// <param name="sourcefilename">The desired full name (with path) of the source file. (The C++ source will be saved here).</param>
        /// <param name="overwrite">If the source file already exists on disk, this parameter sets if it should be overwritten.</param>
        /// <param name="isDll">Sets if the output is a DLL.</param>
        /// <returns>Returns the standard output contents. null if file exists and it cannot be overwritten.</returns>
        public static string[] CompileFromSource(string source, string sourcefilename, bool overwrite, bool isDll)
        {
            if (File.Exists(sourcefilename))
            {
                if (!overwrite)
                {
                    return null;
                }
            }
            File.WriteAllText(sourcefilename, source);
            if (isDll)
            {
                return cl.CompileDll(sourcefilename);
            }
            else
            {
                return cl.Compile(sourcefilename);
            }
        }
        /// <summary>
        /// Compiles the given C++ source in the system temporary folder.
        /// </summary>
        /// <param name="source">The C++ source code.</param>
        /// <param name="isDll">Sets if the output is DLL</param>
        /// <returns>Returns a three-element string array, in which the contents are respectively: the standard output contents from cl, the full path to the generated assembly and the full path to the saved source code.</returns>
        public static string[] CompileFromSourceInTemp(string source, bool isDll, string dname)
        {
            string TMP = Paths.GetTempPath();
            string[] r = cl.CompileFromSource(source, TMP + dname + ".cpp", true, isDll);
            string[] returns =
            {
                r[0],r[1],
                (dname+(isDll ? ".dll" : ".exe")),
                (TMP + dname + ".cpp")
            };
            return returns;
        }
    }
}
